﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI;
using System.Reflection;
using System.Web.Compilation;
using System.Web;
using System.IO;
using System.Text.RegularExpressions;

namespace ExtraClub.Domain.Framework {
    // TODO map in cache control path and type
    public static class UserControlLoader {
        public static string LoadSource(string path) {
            return UserControlLoader<BaseUserControl>.LoadSource(path);
        }
        public static string LoadSource(Control ctl) {
            if (ctl == null) throw new ArgumentNullException("ctl");

            // get the control's page or create new instance of a page from the control's type itself
            Page page = ctl.Page ?? GetPageInstance(GetPageBaseTypeFromControlType(ctl.GetType()));
            page.Controls.Add(ctl);
            return UserControlLoader.ExecutePage(page);
        }

        public static Control LoadControl(string path) {
            return UserControlLoader<Control>.LoadControl(path);
        }
        public static Control LoadControl(string path, Page page) {
            return UserControlLoader<Control>.LoadControl(path, page);
        }
        public static Control LoadControl(string path, bool fireLoadEvents) {
            return UserControlLoader<Control>.LoadControl(path, fireLoadEvents);
        }
        public static Control LoadControl(string path, bool fireLoadEvents, Page page) {
            return UserControlLoader<Control>.LoadControl(path, fireLoadEvents, page);
        }

        private static Regex cleaner = new Regex(@"[\r\n\t](?=[\r\n\t])");
        private static string CleanSource(string source) {
            return cleaner.Replace(source, "");
        }

        internal static Type GetPageBaseTypeFromControlType(Type controlType) {
            Type pageType = null;
            foreach (PropertyInfo prop in controlType.GetProperties()) {
                if (prop.Name == "Page") {
                    pageType = prop.PropertyType;
                    break;
                }
            }
            return pageType;
        }
        internal static Type GetPageBaseTypeFromControlPath(string path) {
            Type pageType = null;

            if (!String.IsNullOrEmpty(path)) {
                object instance = null;
                try {
                    instance = BuildManager.CreateInstanceFromVirtualPath(path, typeof(Control));
                } catch (Exception ex) {
                    throw new Exception("Could not load control '" + path + "'\n" + ex.Message, ex);
                }

                Control ctl = (Control)instance;
                pageType = GetPageBaseTypeFromControlType(instance.GetType());
            }
            return pageType;
        }

        internal static Page GetPageInstance(Type pageType) {
            Page page = null;
            object instance = Activator.CreateInstance(pageType);
            if (instance == null) throw new Exception("Could not create page instance.\nPage type: " + pageType.Name);

            page = instance as Page;

            // it's not ajax if the handler is a page
            bool isAjax = !(HttpContext.Current.CurrentHandler is Page);
            page.Items["IsAjaxSourceCall"] = isAjax;

            if (page == null) throw new Exception("Could not cast " + pageType.Name + " to a Page.");
            return page;

            // if the current hander is a page with the same type of the control serve it
            // TODO: something's wrong when this one's happening
            //if (false && HttpContext.Current.CurrentHandler.GetType().IsSubclassOf(pageType)) page=(Page)HttpContext.Current.CurrentHandler;
            //else { // we're in asmx or so
            //}
        }

        public static void FireLoadEvents(Control ctl) {
            if (ctl == null) throw new ArgumentNullException("ctl");
            FireLoadEvents(ctl, false);
        }
        public static void FireLoadEvents(Control ctl, bool execute) {
            if (ctl == null) throw new ArgumentNullException("ctl");
            if (ctl.Page == null) throw new ArgumentException("ctl", "The control must be inside a page");
            FireLoadEvents(ctl, ctl.Page, execute);
        }
        /// <summary>
        /// Adds the control into a page, and in that way 
        /// </summary>
        /// <param name="ctl"></param>
        /// <param name="page"></param>
        /// <param name="execute"></param>
        internal static void FireLoadEvents(Control ctl, Page page, bool execute) {
            page.Controls.Add(ctl);
            if (execute) HttpContext.Current.Server.Execute(page, new StringWriter(), true);
        }

        #region Internals
        internal static string ExecutePage(Page page) {
            StringWriter stringWriter = new StringWriter();

            HttpContext.Current.Server.Execute(page, stringWriter, true);

            string html = CleanSource(stringWriter.ToString().Trim());

            return html;
        }
        #endregion
    }

    public static class UserControlLoader<TControl>
        where TControl : class {

        #region Helpers
        private static Type GetPageBaseTypeFromControlPath(string path) {
            Type pageType = UserControlLoader.GetPageBaseTypeFromControlPath(path);
            if (pageType == null) {
                pageType = UserControlLoader.GetPageBaseTypeFromControlType(typeof(TControl));
            }
            return pageType;
        }
        private static Page GetPageInstanceFromControlPath(string path) {
            Type pageType = GetPageBaseTypeFromControlPath(path);
            return UserControlLoader.GetPageInstance(pageType);
        }
        #endregion

        #region Delegates
        public delegate void SetControlParameters(TControl ctl);
        #endregion

        #region Load Control
        public static TControl LoadControl(string path) {
            return LoadControl(path, false, null);
        }
        public static TControl LoadControl(string path, bool fireLoadEvents) {
            return LoadControl(path, fireLoadEvents, null);
        }
        public static TControl LoadControl(string path, Page page) {
            return LoadControl(path, false, page);
        }
        public static TControl LoadControl(string path, bool fireLoadEvents, Page page) {
            return LoadControlInternal(path, fireLoadEvents, null, ref page);
        }

        public static TControl LoadControl(string path, SetControlParameters setControlParameters) {
            return LoadControl(path, setControlParameters, null);
        }
        public static TControl LoadControl(string path, SetControlParameters setControlParameters, Page page) {
            return LoadControlInternal(path, false, setControlParameters, ref page);
        }

        public static TControl LoadControl(string path, bool fireLoadEvents, SetControlParameters setControlParameters, Page page) {
            return LoadControlInternal(path, fireLoadEvents, setControlParameters, ref page);
        }

        private static TControl LoadControlInternal(string path, bool fireLoadEvents, SetControlParameters setControlParameters, ref Page page) {
            bool execute = false;

            if (page == null) {
                page = GetPageInstanceFromControlPath(path);
                execute = true;
            }

            TControl ctl = page.LoadControl(path) as TControl;

            if (setControlParameters != null) setControlParameters(ctl);

            if (fireLoadEvents) UserControlLoader.FireLoadEvents(ctl as Control, page, execute);

            return ctl;
        }
        #endregion

        #region Load Source
        public static string LoadSource(string path) {
            return LoadSource(path, null);
        }
        public static string LoadSource(string path, SetControlParameters setControlParameters) {
            Page page = null;
            TControl ctl = LoadControlInternal(path, false, null, ref page);

            if (setControlParameters != null) setControlParameters(ctl);

            page.Controls.Add(ctl as Control);

            return UserControlLoader.ExecutePage(page);
        }
        #endregion
    }
}
